package com.orange.lswidget;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

import org.xmlpull.v1.XmlPullParser;

import android.app.Service;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.AttributeSet;
import android.util.Xml;
import android.widget.RemoteViews;

import com.orange.lockscreen.server.ILSWidgetCallback;
import com.orange.util.LogHelper;

public class LSWidgetService extends Service {

	private static final String		TAG					= "LSWidgetService";
	private static final String		ACTION_READY		= "com.orange.lswidget.system_ready";

	private boolean					mIsLoaded			= false;
	private boolean					mIsAllEnabled		= false;
	private ILSWidgetCallback		mCallback			= null;
	private Context					mContext			= null;
	private PackageManager			mPackageManager		= null;
	private static List<Provider>	sInstalledProviders	= new ArrayList<Provider>();

	static class Provider {
		private AppWidgetProviderInfo	info;
		private int						viewType;
		private boolean					isInitialzed;
		private boolean					hasContent;
		private RemoteViews				views;
	}

	@Override
	public void onCreate() {
		LogHelper.v(TAG, "onCreate. ");
		super.onCreate();
		mContext = getApplicationContext();
		mPackageManager = mContext.getPackageManager();
	}

	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		LogHelper.v(TAG, "onStart. ");
		if (!mIsLoaded) {
			LSWidgetManager.setLSWidgetService(this);
			loadLSWidgetList();
			sendInitialBroadcasts();
		} else {
			broadcastSystemReady();
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		LogHelper.v(TAG, "onBind. ");
		return mBinder;
	}

	@Override
	public boolean onUnbind(Intent intent) {
		LogHelper.v(TAG, "onUnbind. ");
		return super.onUnbind(intent);
	}

	@Override
	public void onDestroy() {
		LogHelper.v(TAG, "onDestroy. ");
		LSWidgetManager.setLSWidgetService(null);
		sInstalledProviders.clear();
		mIsLoaded = false;
		super.onDestroy();
	}

	void loadLSWidgetList() {
		LogHelper.v(TAG, "loadLSWidgetList. ");
		PackageManager pm = mPackageManager;

		Intent intent = new Intent(LSWidgetManager.ACTION_LSWIDGET_UPDATE);
		List<ResolveInfo> broadcastReceivers = pm.queryBroadcastReceivers(
				intent, PackageManager.GET_META_DATA);

		final int N = broadcastReceivers == null ? 0 : broadcastReceivers
				.size();
		ResolveInfo ri = null;
		for (int i = 0; i < N; i++) {
			ri = broadcastReceivers.get(i);
			addProviderLocked(ri);
		}
		mIsLoaded = true;
		if (N == 0) {
			broadcastSystemReady();
		}
		LogHelper.v(TAG, "loadLSWidgetList. finished");
	}

	void addProviderLocked(ResolveInfo ri) {
		LogHelper.v(TAG, "addProviderLocked:  pkgName="
				+ ri.activityInfo.packageName + "  className="
				+ ri.activityInfo.name);
		Provider provider = parseProviderInfoXml(new ComponentName(
				ri.activityInfo.packageName, ri.activityInfo.name), ri);
		if (provider != null) {
			setProviderType(provider);
			sInstalledProviders.add(provider);
			LogHelper.v(
					TAG,
					"addProviderLocked. Add component:"
							+ provider.info.provider + ", label="
							+ provider.info.label + ", icon=0x"
							+ Integer.toHexString(provider.info.icon)
							+ ", initialLayout=0x"
							+ Integer.toHexString(provider.info.initialLayout));
		}
	}

	/**
	 * parse the xml file content to a Provider class instance
	 * @param component
	 * @param ri
	 * @return
	 */
	private Provider parseProviderInfoXml(ComponentName component,
			ResolveInfo ri) {
		Provider p = null;

		ActivityInfo activityInfo = ri.activityInfo;
		XmlResourceParser parser = null;
		try {
			parser = activityInfo.loadXmlMetaData(mPackageManager,
					LSWidgetManager.META_DATA_LSWIDGET_PROVIDER);
			if (parser == null) {
				LogHelper.w(TAG, "parseProviderInfoXml. No "
						+ LSWidgetManager.META_DATA_LSWIDGET_PROVIDER
						+ " meta-data for " + "LS Widget provider '"
						+ component + '\'');
				return null;
			}

			AttributeSet attrs = Xml.asAttributeSet(parser);

			// int type;
			// while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
			// && type != XmlPullParser.START_TAG) {
			// // drain whitespace, comments, etc.
			// }
			int type = parser.next();
			while (type != XmlPullParser.END_DOCUMENT
					&& type != XmlPullParser.START_TAG) {
				// drain whitespace, comments, etc.
				type = parser.next();
			}

			String nodeName = parser.getName();
			if (!"lswidget-provider".equals(nodeName)) {
				LogHelper
						.w(TAG,
								"parseProviderInfoXml. Meta-data does not start with appwidget-provider tag for"
										+ " AppWidget provider '"
										+ component
										+ '\'');
				return null;
			}

			p = new Provider();
			AppWidgetProviderInfo info = p.info = new AppWidgetProviderInfo();

			info.provider = component;

			Resources res = mPackageManager
					.getResourcesForApplication(activityInfo.applicationInfo);

			TypedArray sa = res.obtainAttributes(attrs,
					R.styleable.LSWidgetProviderInfo);

			info.initialLayout = sa.getResourceId(
					R.styleable.LSWidgetProviderInfo_initialLayout, 0);
			
			LogHelper.d(TAG, "initialLayout=" + info.initialLayout);
			
			info.label = activityInfo.loadLabel(mPackageManager).toString();
			info.icon = ri.getIconResource();
			sa.recycle();
		} catch (Exception e) {
			// Ok to catch Exception here, because anything going wrong because
			// of what a client process passes to us should not be fatal for the
			// system process.
			LogHelper.e(TAG,
					"parseProviderInfoXml. XML parsing failed for AppWidget provider '"
							+ component + '\'', e);
			return null;
		} finally {
			if (parser != null)
				parser.close();
		}

		// Fix the originally init layout not used issue, 
		// which could cause "Error inflat widget..." on the 
		// event and music widgets.
		p.views = new RemoteViews(this.getPackageName(), p.info.initialLayout);
		return p;
	}

	private void setProviderType(Provider provider) {
		if (provider.info.provider != null) {
			String className = provider.info.provider.getClassName();
			int type = LSWidgetTypeIniter.getTypeByClassName(className);
			provider.viewType = type;
			LogHelper.v(
					TAG,
					"setProviderType. provider type=[0x"
							+ Integer.toHexString(type) + "]");
		}
	}

	void sendInitialBroadcasts() {
		sendEnableIntentLocked();
		sendUpdateIntentLocked();
	}

	void sendEnableIntentLocked() {
		LogHelper.v(TAG, "sendEnableIntentLocked. ");
		int N = sInstalledProviders.size();
		Provider provider = null;
		for (int i = 0; i < N; i++) {
			provider = sInstalledProviders.get(i);
			if (null != provider && null != provider.info) {
				Intent intent = new Intent(
						LSWidgetManager.ACTION_LSWIDGET_ENABLED);
				intent.setComponent(provider.info.provider);
				mContext.sendBroadcast(intent);
			}
		}
	}

	void sendUpdateIntentLocked() {
		LogHelper.v(TAG, "sendUpdateIntentLocked. ");
		int N = sInstalledProviders.size();
		Provider provider = null;
		for (int i = 0; i < N; i++) {
			provider = sInstalledProviders.get(i);
			if (null != provider && null != provider.info) {
				Intent intent = new Intent(
						LSWidgetManager.ACTION_LSWIDGET_UPDATE);
				intent.setComponent(provider.info.provider);
				intent.putExtra(LSWidgetManager.EXTRA_LSWIDGET_TYPE,
						provider.viewType);
				LogHelper.v(
						TAG,
						"sendUpdateIntentLocked [type=0x"
								+ Integer.toHexString(provider.viewType) + "]");
				mContext.sendBroadcast(intent);
			}
		}
	}

	public List<AppWidgetProviderInfo> getInstalledProviders() {
		LogHelper.v(TAG, "getInstalledProviders. mInstalledProviders: "
				+ sInstalledProviders.size());

		if (sInstalledProviders == null || sInstalledProviders.isEmpty()) {
			LogHelper
					.w(TAG,
							"getInstalledProviders. Query error when get installed providers");
			return null;
		}

		final int N = sInstalledProviders.size();
		ArrayList<AppWidgetProviderInfo> result = new ArrayList<AppWidgetProviderInfo>(
				N);
		for (int i = 0; i < N; i++) {
			Provider p = sInstalledProviders.get(i);
			result.add(p.info);
		}
		return result;
	}

	public int[] getInstalledProviderType() {
		int N = sInstalledProviders.size();
		int[] viewTypes = new int[N];

		Provider provider;
		for (int i = 0; i < N; i++) {
			provider = sInstalledProviders.get(i);
			viewTypes[i] = provider.viewType;
		}

		return viewTypes;
	}

	public AppWidgetProviderInfo getProviderInfoByType(int type) {

		Provider provider = null;
		int N = sInstalledProviders.size();
		for (int i = 0; i < N; i++) {
			provider = sInstalledProviders.get(i);
			if (provider.viewType == type) {
				return provider.info;
			}
		}

		return null;
	}

	public RemoteViews getLSWidgetView(int viewType) {
		Provider provider = null;
		int N = sInstalledProviders.size();
		for (int i = 0; i < N; i++) {
			provider = sInstalledProviders.get(i);
			if (provider.viewType == viewType) {
				return provider.views;
			}
		}

		return null;
	}

	public boolean isLSWidgetViewHasContent(int viewType) {
		Provider provider = null;
		int N = sInstalledProviders.size();
		for (int i = 0; i < N; i++) {
			provider = sInstalledProviders.get(i);
			if (provider.viewType == viewType) {
				return provider.hasContent;
			}
		}

		return true;
	}

	void updateLSWidgetByType(int viewType, RemoteViews views,
			boolean hasContent) {
		LogHelper.v(TAG, "updateLSWidgetByType. ");
		Provider provider = null;
		int N = sInstalledProviders.size();
		for (int i = 0; i < N; i++) {
			provider = sInstalledProviders.get(i);
			if (provider.viewType == viewType) {
				provider.views = views;
				provider.hasContent = hasContent;
				if (!provider.isInitialzed) {
					provider.isInitialzed = true;
				}
				LogHelper.v(TAG,
						"updateLSWidgetByType. Got RemoteViews by ViewType=[0x"
								+ Integer.toHexString(viewType) + "]");
			}
		}

		reqSystemReady();
	}

	public void updateLSWidgetType(int viewType, RemoteViews views,
			boolean hasContent) {
		updateLSWidgetByType(viewType, views, hasContent);
	}

	/**
	 * call LockscreenService to update the LockscreenView 
	 * @param provider
	 * @param views
	 * @param hasContent
	 */
	void updateLSWidgetByProvider(ComponentName provider, RemoteViews views,
			boolean hasContent) {
		LogHelper.v(TAG, "updateLSWidgetByProvider. ");
		int N = sInstalledProviders.size();
		Provider p = null;
		for (int i = 0; i < N; i++) {
			p = sInstalledProviders.get(i);
			ComponentName oraginal = p.info.provider;
			if (oraginal.getPackageName().equals(provider.getPackageName())
					&& oraginal.getClassName().equals(provider.getClassName())) {
				p.views = views;
				p.hasContent = hasContent;
				if (null != mCallback) {
					try {
						mCallback.updateLSWidget(views, p.viewType,
								p.hasContent);
					} catch (RemoteException e) {
						LogHelper.e(TAG, "updateLSWidgetByProvider. ", e);
					}
				}
				LogHelper.v(TAG,
						"updateLSWidgetByProvider. Got RemoteViews by ComponentName=["
								+ provider + "]");
			}
		}
	}

	public void updateLSWidgetProvider(ComponentName provider,
			RemoteViews views, boolean hasContent) {
		updateLSWidgetByProvider(provider, views, hasContent);
	}

	void reqSystemReady() {
		if (!mIsAllEnabled) {
			boolean ready = false;
			int count = 0;
			int N = sInstalledProviders.size();
			Provider provider = null;
			for (int i = 0; i < N; i++) {
				provider = sInstalledProviders.get(i);
				if (provider.isInitialzed) {
					count++;
				}
			}
			if (N > 0 && count == N) {
				ready = true;
				mIsAllEnabled = true;
			}
			if (ready) {
				broadcastSystemReady();
			}
		}
	}

	void broadcastSystemReady() {
		Intent intent = new Intent();
		intent.setAction(ACTION_READY);
		mContext.sendBroadcast(intent);
	}

	public void startListening(ILSWidgetCallback callback) {
		LogHelper.v(TAG, "startListening. ");
		mCallback = callback;
	}

	public void stopListening() {
		LogHelper.v(TAG, "stopListening. ");
		mCallback = null;
	}

	static class ServiceStub extends ILSWidgetService.Stub {
		private WeakReference<LSWidgetService>	mService;

		// ServiceStub(LSWidgetService service) {
		// mService = new WeakReference<LSWidgetService>(service);
		// }
		public ServiceStub(LSWidgetService service) {
			super();
			mService = new WeakReference<LSWidgetService>(service);
		}

		@Override
		public List<AppWidgetProviderInfo> getInstalledProviders()
				throws RemoteException {
			return mService.get().getInstalledProviders();
		}

		@Override
		public RemoteViews getLSWidgetView(int viewType) {
			return mService.get().getLSWidgetView(viewType);
		}

		@Override
		public boolean isLSWidgetViewHasContent(int viewType)
				throws RemoteException {
			return mService.get().isLSWidgetViewHasContent(viewType);
		}

		@Override
		public void startListening(ILSWidgetCallback callback) {
			mService.get().startListening(callback);
		}

		@Override
		public void stopListening() {
			mService.get().stopListening();
		}

		@Override
		public int[] getInstalledProviderType() {
			return mService.get().getInstalledProviderType();
		}

		@Override
		public AppWidgetProviderInfo getProviderInfoByType(int type) {
			return mService.get().getProviderInfoByType(type);
		}
	}

	private final IBinder	mBinder	= new ServiceStub(this);
}
